﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data.OleDb;
using System.Data;
using System.IO;
using System.Reflection;
using Microsoft.Build.BuildEngine;
using EnvDTE100;
using EnvDTE;
using System.Runtime.InteropServices;
using Microsoft.Build.Evaluation;

namespace Mdb2RIA
{
    public class MdbDatabase : IDisposable
    {
        public List<MdbTable> Tables { get; private set; }

        private DbConnection connection;
        public string Name { get; private set; }
        private List<string> fileList = new List<string>();

        public MdbDatabase(string connectionString)
        {
            Tables = new List<MdbTable>();

            ExtractDatabaseName(connectionString);

            connection = new OleDbConnection(connectionString);
            connection.Open();

            GenerateSchema();
        }

        private void GenerateProject(string path)
        {
            Engine buildEngine = new Engine();

            Microsoft.Build.BuildEngine.Project project = buildEngine.CreateNewProject();
            project.DefaultTargets = "Build";
            project.DefaultToolsVersion = "4.0";

            BuildPropertyGroup props = project.AddNewPropertyGroup(false);
            props.AddNewProperty("AssemblyName", Name);
            props.AddNewProperty("OutputType", "Library");
            props.AddNewProperty("Platform", "x86");
            props.AddNewProperty("Configuration", "Debug");
            props.AddNewProperty("TargetFrameworkVersion", "v4.0");
            props.AddNewProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}");
            props.AddNewProperty("ProductVersion", "8.0.30703");
            props.AddNewProperty("SchemaVersion", "2.0");

            //add debug and release
            BuildPropertyGroup debugProps = project.AddNewPropertyGroup(false);
            debugProps.Condition = " '$(Configuration)|$(Platform)' == 'Debug|x86' ";
            debugProps.AddNewProperty("PlatformTarget", "x86");
            debugProps.AddNewProperty("DebugSymbols", "true");
            debugProps.AddNewProperty("DebugType", "full");
            debugProps.AddNewProperty("Optimize", "false");
            debugProps.AddNewProperty("OutputPath", @"bin\Debug\");
            debugProps.AddNewProperty("DefineConstants", "DEBUG;TRACE");
            debugProps.AddNewProperty("ErrorReport", "prompt");
            debugProps.AddNewProperty("WarningLevel", "4");

            BuildPropertyGroup releaseProps = project.AddNewPropertyGroup(false);
            releaseProps.Condition = " '$(Configuration)|$(Platform)' == 'Release|x86' ";
            releaseProps.AddNewProperty("PlatformTarget", "x86");
            releaseProps.AddNewProperty("DebugType", "pdbonly");
            releaseProps.AddNewProperty("Optimize", "true");
            releaseProps.AddNewProperty("OutputPath", @"bin\Release\");
            releaseProps.AddNewProperty("DefineConstants", "TRACE");
            releaseProps.AddNewProperty("ErrorReport", "prompt");
            releaseProps.AddNewProperty("WarningLevel", "4");

            BuildItemGroup items = project.AddNewItemGroup();
            //Add References
            items.AddNewItem("Reference", "System");
            items.AddNewItem("Reference", "System.ComponentModel.DataAnnotations");
            items.AddNewItem("Reference", "System.Data.Entity");
            items.AddNewItem("Reference", "System.Runtime.Serialization");
            items.AddNewItem("Reference", "System.Security");
            items.AddNewItem("Reference", "System.ServiceModel.DomainServices.Hosting");
            items.AddNewItem("Reference", "System.ServiceModel.DomainServices.Server");
            items.AddNewItem("Reference", "System.Web");
            items.AddNewItem("Reference", "System.configuration");
            items.AddNewItem("Reference", "System.Data");
            items.AddNewItem("Reference", "System.Xml");
            items.AddNewItem("Reference", "System.Xml.Linq");
            items.AddNewItem("Reference", "System.Transactions");

            //Add Compile Files
            foreach (string file in fileList)
            {
                items.AddNewItem("Compile", file);
            }

            project.AddNewImport(@"$(MSBuildToolsPath)\Microsoft.CSharp.targets", null);

            project.Save(Path.Combine(path, Name + ".csproj"));
        }

        private void GenerateSolution(string path)
        {
            Type objType = Type.GetTypeFromProgID("VisualStudio.DTE.10.0");
            DTE dteObj = (DTE)Activator.CreateInstance(objType);

            string solutionPath = Path.Combine(path, Name) + ".sln";

            dteObj.Solution.Create(solutionPath, Name);

            dteObj.Solution.AddFromFile(Path.Combine(path, Name + ".csproj"));

            dteObj.Solution.SaveAs(solutionPath);
        }

        

        private void ExtractDatabaseName(string connectionString)
        {
            //extract database name
            string[] splittedConnectionString = connectionString.Split(';');
            foreach (string splittedPart in splittedConnectionString)
            {
                string[] nameAndValue = splittedPart.Split('=');
                string name = nameAndValue[0];
                string value = nameAndValue[1];

                if (name == "Data Source")
                {
                    Name = Path.GetFileNameWithoutExtension(value);
                }
            }
        }

        private void GenerateSchema()
        {
            string[] restrictions = new string[4];
            restrictions[3] = "Table";

            DataTable tables = connection.GetSchema("Tables", restrictions);

            for (int i = 0; i < tables.Rows.Count; i++)
            {
                Tables.Add(new MdbTable(connection, tables.Rows[i][2].ToString()));
            }
        }

        public void GenerateRIAService()
        {
            //main file

            StringBuilder mainFile = new StringBuilder();

            Utilities.AppendMultipleLines(
                mainFile,
                "using System;",
                "using System.Collections.Generic;",
                "using System.ComponentModel;",
                "using System.ComponentModel.DataAnnotations;",
                "using System.Linq;",
                "using System.ServiceModel.DomainServices.Hosting;",
                "using System.ServiceModel.DomainServices.Server;",
                "using System.Xml.Linq;",
                "using System.Configuration;",
                "using System.Web.Configuration;",
                "using System.Data.Common;",
                "using System.Data.OleDb;",
                "using System.Data;",
                "using System.Reflection;",
                "using System.Transactions;"
            );

            mainFile.AppendLine();
            mainFile.AppendLine("namespace " + Configuration.Instance.NamespaceName);
            mainFile.AppendLine("{");
            mainFile.AppendLine("public class " + Name + " : DomainService");
            mainFile.AppendLine("{");
            mainFile.AppendLine("private DbConnection connection;");
            mainFile.AppendLine("private const string connectionString = @\"" + connection.ConnectionString + "\";\r\n");

            mainFile.AppendLine(
                MethodGenerator.GenerateMethod(
                    new string[0],
                    "Initialize",
                    "void",
                    "DomainServiceContext context",
                    "base.Initialize(context);\r\nconnection = new OleDbConnection(connectionString);\r\nusing (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))\r\n{\r\nconnection.Open();\r\n}\r\n",                    
                    MethodGenerator.MethodPrefixes.Public,
                    MethodGenerator.MethodPrefixes.Override
                )
            );

            mainFile.AppendLine(
                MethodGenerator.GenerateMethod(
                    new string[0],
                    "Count<T>",
                    "int",
                    "IQueryable<T> query",
                    "return query.Count();",                    
                    MethodGenerator.MethodPrefixes.Protected,
                    MethodGenerator.MethodPrefixes.Override
                )
            );

            mainFile.AppendLine(
                MethodGenerator.GenerateMethod(
                    new string[0],
                    "GetData<T>",
                    "List<T>",
                    "",
                    "DbCommand command = connection.CreateCommand();\r\n" +
                    "command.CommandText = \"SELECT * FROM \" + typeof(T).Name;\r\n\r\n" +
                    "List<T> data = new List<T>();\r\n\r\n" +
                    "DbDataReader reader = command.ExecuteReader();\r\n\r\n" +
                    "//iterate over records in db, create object for each one and fill it with data from reader\r\n" +
                    "while (reader.Read())\r\n{\r\nT instance = Activator.CreateInstance<T>();\r\n" +
                    "PropertyInfo[] propsInfo = instance.GetType().GetProperties();\r\n\r\n" +
                    "foreach (PropertyInfo property in propsInfo)\r\n" +
                    "{\r\n" +
                    "property.GetSetMethod().Invoke(instance, new object[] { reader[property.Name] });\r\n" +
                    "}\r\n\r\n" +
                    "data.Add(instance);\r\n" +
                    "}\r\n" +
                    "return data;",
                    MethodGenerator.MethodPrefixes.Private
                )
            );


            foreach (MdbTable table in Tables)
            {
                string tableFileName = table.Name + ".cs";

                File.WriteAllText(tableFileName, Utilities.FormatDocument(table.GenerateTableType()));

                mainFile.AppendLine(
                    MethodGenerator.GenerateMethod(
                        new string[] { "[Query(IsDefault=true)]" },
                        "Get" + table.Name,
                        "IQueryable<" + table.Name + ">",
                        "",
                        "return GetData<" + table.Name + ">().AsQueryable();",
                        MethodGenerator.MethodPrefixes.Public
                    )
                );

                fileList.Add(tableFileName);
            }

            mainFile.AppendLine("}");
            mainFile.AppendLine("}");

            string dbFileName = Name + ".cs";

            File.WriteAllText(dbFileName, Utilities.FormatDocument(mainFile.ToString()));
            fileList.Add(dbFileName);

            GenerateProject("");
            //GenerateSolution("");
        }

        public void Dispose()
        {
            if (connection != null)
            {
                connection.Close();
                connection.Dispose();
            }
        }
    }
}
